package cn.hallele.leinsteining.ms.infrastructure.plugin.safety.encoder;

import cn.hallele.leinsteining.mc.infrastructure.metadata.constants.MsgCodeConst;
import cn.hallele.leinsteining.mc.infrastructure.metadata.exceptions.children.EncodeException;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.nio.charset.StandardCharsets;

/**
 * BBTSCoreApp
 *
 * @author anle5
 * @since 2023/11/29 星期三 22:57
 */
@Slf4j
public class RSAPasswordEncoder implements PasswordEncoder {
    private RSA rsa;

    /**
     * RSAPasswordEncode
     */
    public RSAPasswordEncoder() {
    }

    /**
     * rsa公私钥
     *
     * @param publicKey  公钥
     * @param privateKey 私钥
     */
    public RSAPasswordEncoder(String publicKey, String privateKey) {
        rsa = SecureUtil.rsa(privateKey, publicKey);
    }

    /**
     * Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
     * greater hash combined with an 8-byte or greater randomly generated salt.
     *
     * @param rawPassword 原密码
     */
    @Override
    public String encode(CharSequence rawPassword) {
        return rsa.encryptHex(rawPassword.toString().getBytes(StandardCharsets.UTF_8), KeyType.PublicKey);
    }

    /**
     * Verify the encoded password obtained from storage matches the submitted raw
     * password after it too is encoded. Returns true if the passwords match, false if
     * they do not. The stored password itself is never decoded.
     *
     * @param rawPassword     the raw password to encode and match, 用户输入的
     * @param encodedPassword the encoded password from storage to compare with, 数据库储存的
     * @return true if the raw password, after encoding, matches the encoded password from
     * storage
     */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (rawPassword == null) {
            log.error("Input password is null.");
            throw new EncodeException(MsgCodeConst.ENCRYPT_OR_DECRYPT_ERROR);
        }
        if (StringUtils.isBlank(encodedPassword)) {
            log.error("Empty encoded password");
            throw new EncodeException(MsgCodeConst.ENCRYPT_OR_DECRYPT_ERROR);
        }
        try {
            String clearRawPassword = StrUtil.str(rsa.decrypt(rawPassword.toString(), KeyType.PrivateKey), CharsetUtil.UTF_8);
            String clearEncodedPassword = StrUtil.str(rsa.decrypt(encodedPassword, KeyType.PrivateKey), CharsetUtil.UTF_8);
            return clearRawPassword.equals(clearEncodedPassword);
        } catch (Exception e) {
            log.error("Encryption or decryption exception", e);
            throw new EncodeException(MsgCodeConst.ENCRYPT_OR_DECRYPT_ERROR);
        }
    }
}